package org.framework.mvc.servlet;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.framework.mvc.annotation.*;
import org.framework.mvc.handler.Handler;
import org.framework.mvc.utils.ResolveUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName: MyDispacherServlet
 * @Auther: Jerry
 * @Date: 2020/7/13 14:15
 * @Desctiption: TODO
 * @Version: 1.0
 */
public class MyDispacherServlet extends HttpServlet {

    private final String BASE_PAKAGE = "base-package";

    private Properties properties = new Properties();

    private List<String> beanDefinitions = new ArrayList<>();

    private Map<String, Object> beanDefinitionMap = new HashMap<>();

    private List<Handler> handlerRequestMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        try {
            // 加载配置文件
            doLoadConfig(config.getInitParameter("configLocation"));

            // 包扫描，扫描注解
            doScan(properties.getProperty(BASE_PAKAGE));
            // bean初始化
            doRegister();
            // 维护依赖关系，即Autowired依赖注入
            doAutowired();
            //初始化处理器映射器，建立url和method之间的映射关系（HandlerMapping）
            initHandlerMapping();
            //等待请求，处理请求
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载配置信息
     * @param configLocation 配置文件路径
     */
    private void doLoadConfig(String configLocation) {
        InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(configLocation);
        try {
            Document root = new SAXReader().read(resourceAsStream);
            Element rootElement = root.getRootElement();
            List<Element> list = rootElement.selectNodes("//component-scan");
            for (Element element : list) {
                String basePackage = element.attributeValue(BASE_PAKAGE);
                properties.setProperty(BASE_PAKAGE, basePackage);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描所有类文件
     * @param scanPackage 扫描包
     */
    private void doScan(String scanPackage){
        // 加载资源
        URL resource = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\\.", "/"));
        File file = new File(resource.getFile());

        File[] files = file.listFiles();
        for (File f : files) {
            // 如果是文件夹，递归调用，获取子文件夹的内容，直到文件为止
            if (f.isDirectory()) {
                doScan(scanPackage + "." + f.getName());
            } else {
                String className = scanPackage + "." + f.getName().replace(".class", "");
                beanDefinitions.add(className);
            }
        }
    }

    /**
     * 将添加注解的类注册为bean对象
     */
    private void doRegister() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (beanDefinitions.isEmpty()) {return;}
        for (String beanDefinition : beanDefinitions) {
            Class<?> aClass = Class.forName(beanDefinition);
            // 判断是否有注解标记
            if (aClass.isAnnotationPresent(MyController.class)) {
                MyController controller = aClass.getAnnotation(MyController.class);

                String beanName = controller.value().trim();
                if ("".equals(beanName)) {
                    beanName = ResolveUtils.lowerFirst(aClass.getSimpleName());
                }
                // 将bean存放到ioc容器中
                beanDefinitionMap.put(beanName, aClass.newInstance());
            } else if (aClass.isAnnotationPresent(MyService.class)) {
                MyService service = aClass.getAnnotation(MyService.class);

                String beanName = service.value().trim();
                if ("".equals(beanName)) {
                    beanName = ResolveUtils.lowerFirst(aClass.getSimpleName());
                }
                // 将bean存放到ioc容器中
                beanDefinitionMap.put(beanName, aClass.newInstance());

                // 获取接口名称
                Class<?>[] interfaces = aClass.getInterfaces();
                for (Class<?> anInterface : interfaces) {
                    // 接口全限定类名作为id放入ioc容器中
                    beanDefinitionMap.put(anInterface.getName(), aClass.newInstance());
                }
            } else {
                continue;
            }
        }
    }

    /**
     * 依赖注入
     */
    private void doAutowired() throws IllegalAccessException {
        if (beanDefinitionMap.isEmpty()) { return; }
        for (Map.Entry<String, Object> entry : beanDefinitionMap.entrySet()) {
            // 遍历每个对象的属性
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (!field.isAnnotationPresent(MyAutoWired.class)) {
                    continue;
                }
                // 获取autowired标记的属性
                MyAutoWired autoWired = field.getAnnotation(MyAutoWired.class);
                String beanName = autoWired.value().trim();
                if ("".equals(beanName)) {
                    beanName = ResolveUtils.lowerFirst(field.getType().getName());
                }
                // 开启赋值
                field.setAccessible(true);
                field.set(entry.getValue(), beanDefinitionMap.get(beanName));
            }
        }
    }

    /**
     * 建立url和handler之间的映射关系
     */
    private void initHandlerMapping(){
        if (beanDefinitionMap.isEmpty()) { return; }
        for (Map.Entry<String, Object> entry : beanDefinitionMap.entrySet()) {
            // 判断类是否标记Controller注解,如果不是controller则跳过
            if (!entry.getValue().getClass().isAnnotationPresent(MyController.class)) {
                continue;
            }

            // 获取标记类的security注解
            String[] securityPath = new String[2];
            if (entry.getValue().getClass().isAnnotationPresent(Security.class)) {
                Security security = entry.getValue().getClass().getAnnotation(Security.class);
                securityPath = security.value();
            }

            // 判断类是否标记RequestMapping注解
            String classPath = "";
            if (entry.getValue().getClass().isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping requestMapping = entry.getValue().getClass().
                        getAnnotation(MyRequestMapping.class);
                // 获取类的uri，如：/demo
                classPath = requestMapping.value().trim();
            }
            Method[] declaredMethods = entry.getValue().getClass().getDeclaredMethods();
            for (Method method : declaredMethods) {
                Handler handler = new Handler();

                // 判断方法是否标记RequestMapping注解
                if (method.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                    // 获取方法的uri，如：/query，将类的uri和方法的uri拼接，得到最终的uri：/demo/query
                    String methodPath = requestMapping.value().trim();
//                    Handler handler = new Handler(entry.getValue(),
//                            Pattern.compile(classPath + methodPath), method);
                    handler.setTarget(entry.getValue());
                    handler.setPattern(Pattern.compile(classPath + methodPath));
                    handler.setMethod(method);
                    Map<String, Integer> indexMapping = new HashMap<>();
                    handler.setIndexMapping(indexMapping);
                    // 获取方法参数位置及设置参数位置
                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        if (HttpServletRequest.class == parameter.getType() ||
                                HttpServletResponse.class == parameter.getType()) {
                            handler.getIndexMapping().put(parameter.getType().getSimpleName(), i);
                        } else {
                            handler.getIndexMapping().put(parameter.getName(), i);
                        }
                    }

                }

                // 获取标记方法的security注解
                if (method.isAnnotationPresent(Security.class)) {
                    Security security = method.getAnnotation(Security.class);
                    // 如果类上标记了注解，方法上也标记了注解，则取方法上的生效
                    securityPath = security.value();
                    // 用做权限校验
                    handler.setSecurity(securityPath);
                }

                // 添加到集合
                handlerRequestMapping.add(handler);
            }

        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 处理请求
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 NOT FOUND");
            return;
        }

        // 获取方法参数类型及绑定参数
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        Object[] params = new Object[parameterTypes.length];

        // 获取请求参数集合并遍历
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            // 不包含此参数
            if (!handler.getIndexMapping().containsKey(entry.getKey())) {
                continue;
            }
            String value = StringUtils.join(entry.getValue(), ",");
            Integer index = handler.getIndexMapping().get(entry.getKey());
            params[index] = value;

            String[] security = handler.getSecurity();
            for (String s : security) {
                if (!s.equals(value)) {
                    resp.setHeader("Content-Type", "text/html;charset=utf-8");
                    resp.setCharacterEncoding("utf-8");
                    resp.getWriter().write("无权访问~");
					return;
                }
            }
        }

        // 设置request参数
        Integer reqIndex = handler.getIndexMapping().get(HttpServletRequest.class.getSimpleName());
        params[reqIndex] = req;

        // 设置response参数
        Integer respIndex = handler.getIndexMapping().get(HttpServletResponse.class.getSimpleName());
        params[respIndex] = resp;

        // 使用代理执行原方法
        Object result = null;
        try {
            result = handler.getMethod().invoke(handler.getTarget(), params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        resp.setHeader("Content-Type", "text/html;charset=utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.getWriter().write(result.toString());
    }

    /**
     * 获取处理器
     * @param request 请求
     * @return {@link Handler}
     */
    private Handler getHandler(HttpServletRequest request){
        if (handlerRequestMapping.isEmpty()) { return null; }
        for (Handler handler : handlerRequestMapping) {
            // 匹配uri，找到对应的handler
            Matcher matcher = handler.getPattern().matcher(request.getRequestURI());
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }

}
